import breve
import random

total_bots = 20
bots_to_exit = 20
exit_scale_factor =.6
# sometimes bots get stuck
# immobiles = 0

class Controller(breve.Control):
    def __init__(self):
        breve.Control.__init__(self)
        self.setBackgroundColor(breve.vector())
        self.enableLighting()
        self.env = Room(breve.vector(20, 0, 20), 8, [], [], [84, 0])
        self.tests_to_run = 10
        self.exited_bots = 0
        self.score = 0
        self.start_time = 0
        self.tests_running = False
        
    def iterate(self):
        if not self.tests_running:
            self.tests_running = True
            self.runTests()
        
        breve.Control.iterate(self)
        self.updateNeighbors()
        
        if self.getTime() - self.start_time > 100:
            self.score = (bots_to_exit - self.exited_bots) * 1000
            self.endTest()
            self.runTests()
        elif self.exited_bots == bots_to_exit:
            self.endTest()
            self.runTests()
        
    def reset(self):
        # remove all bots
        exitBots = breve.allInstances(ExitBot)
        while len(exitBots) > 0:
            breve.deleteInstance(exitBots.pop())
        blocks = breve.allInstances(breve.Stationary)
        while len(blocks) > 0:
            breve.deleteInstance(blocks.pop())
        
        # reset vars
        self.score = 0
        self.exited_bots = 0
    
    def runTests(self):
        if self.tests_to_run >= 0:
            self.tests_to_run -= 1
            self.reset()
            self.startTest()
        else:
            self.endSimulation()
                    
    def startTest(self):
        self.env.placeBots([ExitBot], [total_bots])
        # self.env.placeBots([breve.Stationary], [immobiles])
        block = breve.Stationary()
        block.move(breve.vector(84,4,-32))
        block.setShape(breve.createInstances(breve.Cube, 1).initWith(breve.vector(8,8,8)))
        
        block = breve.Stationary()
        block.move(breve.vector(88,4,-24))
        block.setShape(breve.createInstances(breve.Cube, 1).initWith(breve.vector(8,8,8)))
        
        block = breve.Stationary()
        block.move(breve.vector(80,4,-24))
        block.setShape(breve.createInstances(breve.Cube, 1).initWith(breve.vector(8,8,8)))
        
        self.signalExit()
        self.start_time = self.getTime()

    def endTest(self):
        print self.score/bots_to_exit
    
    def signalExit(self):
        exitBots = breve.allInstances(ExitBot)
        for i in range(0, bots_to_exit):
            exitBots[i].leaveRoom()

class ExitBot(breve.Mobile):
    def __init__(self):
        breve.Mobile.__init__(self)
        self.enablePhysics()
        # should probably not hardcode this
        self.setNeighborhoodSize(8*1.5)
        self.getOut = False
        self.alive = True
        self.neighborList = []
        self.handleCollisions('ExitBot', 'collide')
        
    def collide(self, obj):
        self.setColor(breve.vector(0,0,0))
        self.alive = False
        if self.getOut:
            self.getOut = False
            self.getController().exited_bots += 1
            self.getController().score += 1000
		if obj.getOut:
			obj.getOut = False
			obj.getController().exited_bots += 1
			obj.getController().score += 1000	
    
    def getNeighbors(self):
        return [x for x in breve.Mobile.getNeighbors(self)
                        if not isinstance(x, Obstruction)]
    
    def iterate(self):
        breve.Mobile.iterate(self)     
        self.neighborList = self.getNeighbors()
        # vec = breve.vector(0,0,0)
        vec = breve.randomExpression(breve.vector(2, 0, 2)) - breve.vector(1, 0, 1)
        
        if self.getOut: vec = self.leave()
        else: vec = self.wander()
        
        # AVOIDANCE
        avoid_vector = breve.vector(0, 0, 0)
        for bot in self.neighborList:
            length = (self.getLocation() - bot.getLocation()).length()
            if length != 0:
                heading = (self.getLocation() - bot.getLocation()).normalize()                  
                force = 30/length
                avoid_vector = avoid_vector + heading*force
        
        vec = avoid_vector + vec
        
        if self.alive:
            self.setVelocity(breve.vector(vec[0], 0, vec[2]))
        
    
    def leave(self):
        return (self.exit - self.getLocation()).normalize() * 7
    
    def wander(self):
        return (self.getVelocity().normalize()*5 + (breve.randomExpression(
                breve.vector(2, 0, 2)) - breve.vector(1, 0, 1)).normalize())
    
    def leaveRoom(self):    
        self.getOut = True
        self.setColor(breve.vector(1, 0, 0))
                    
class Room(breve.Real):
    def __init__(self, dim, unit, botTypes, botCounts, exit):
        breve.Real.__init__(self)
        self.unit = unit
        self.width = int(dim[0])
        self.height = int(dim[2])
        self.exit = breve.vector(exit[0], unit / 2, -exit[1])

        self.getController().pointCamera(
            breve.vector(self.width * unit / 2, 0, -self.height * unit / 2), 
            breve.vector(0, 200, self.height * unit))
        
        for x in range(0, self.width):
            Wall(breve.vector(x * unit + unit / 2, unit / 2, 0), 
                 breve.vector(unit, unit, .1))
            Wall(breve.vector(x * unit + unit / 2, unit / 2, -self.height * unit), 
                 breve.vector(unit, unit, .1))
        
        for z in range(0, self.height):
            Wall(breve.vector(0, unit / 2, -z * unit - unit / 2), 
                 breve.vector(.1, unit, unit))
            Wall(breve.vector(self.width * unit, unit / 2, -z * unit - unit / 2), 
                 breve.vector(.1, unit, unit))
                
        Ground(breve.vector((self.width / 2) * unit, 0, -(self.height / 2) * unit), 
                       breve.vector(self.width * unit, .1, self.height * unit)) 
        
        botCount = 0
        for c in range(0, len(botCounts)):
            botCount += botCounts[c]
        
        if botCount > self.width * self.height:
            print "Too many bots!"
        self.placeBots(botTypes, botCounts)
        
        BlackHole(self.exit, breve.vector(exit_scale_factor*unit, unit, .2))

    def placeBots(self, botTypes, botCounts):
        positions = []
        positionCount = self.width * self.height
        
        for i in range(0, positionCount):
            positions.append(True)

        while len(botCounts) > 0:
            while True:
                spot = random.randint(0, positionCount - 1)
                if positions[spot]: break
                
            positions[spot] = False 
            bot = botTypes[0]()
            bot.move(breve.vector(
                int(spot % self.height) * self.unit + self.unit / 2, self.unit / 2, 
               -int(spot / self.height) * self.unit - self.unit / 2))
            bot.unit = self.unit
            bot.setShape(breve.createInstances(breve.Cube, 1).initWith(
                breve.vector(self.unit - 1, self.unit - 1, self.unit - 1)))
            
            if isinstance(bot, ExitBot):
                bot.exit = self.exit
                bot.setShape(breve.createInstances(
                    breve.Sphere, 1).initWith(self.unit / 2 - 1))
            
            botCounts[0] -= 1
            if botCounts[0] == 0:
                botCounts.pop(0)
                botTypes.pop(0)
    

class Obstruction(breve.Stationary):
    def __init__(self, position, dimension, color):
        breve.Stationary.__init__(self)
        self.setTexture(-1)
        self.setShape(breve.createInstances(breve.Cube, 1).initWith(dimension))
        self.setColor(color)
        self.move(position)
    
class Wall(Obstruction):
    def __init__(self, position, dimension):
        Obstruction.__init__(self, position, dimension, breve.vector(1.0, 1.0, 1.0))
        #self.setBitmapTransparency(0.5)    
        
class Ground(Obstruction):
    def __init__(self, position, dimension):
        Obstruction.__init__(self, position, dimension, breve.vector(0.0, 0.3, 0.0))
        #self.setBitmapTransparency(0.5)
        
class BlackHole(Obstruction):
    def __init__(self, position, dimension):
        Obstruction.__init__(self, position, dimension, breve.vector(0, 0, 0))
        self.setNeighborhoodSize(4)
        self.handleCollisions('ExitBot', 'collide')
        
    def collide(self, x):
        if isinstance(x, ExitBot) and x.getOut:
            controller = self.getController()
            controller.exited_bots += 1
            controller.score += controller.getTime() - controller.start_time 
            breve.deleteInstance(x)
        elif isinstance(x, Wall):
            breve.deleteInstance(x)
                
                
        
Controller()
